<?php
/**
 * 
 */

/**
 * Class containing all constants utilized by the Nephtali Framework.
 * @author Adam Richardson of Envision Internet Consulting, LLC
 *
 */
class NC
{
    /**
     * Display errors in the output.
     */
    const CONF_ERRORS_DISPLAY = 9;
    /**
     * Suppress error output to the screen and log the error results.
     */
    const CONF_ERRORS_SUPPRESS_AND_LOG = 10;
    /**
     * Suppress error output to the screen.
     */
    const CONF_ERRORS_SUPPRESS = 11;
    /**
     * Debug capabilities turned off.
     */
    const CONF_DEBUG_OFF = 14;
    /**
     * Debug data appended to the display of the current page.
     */
    const CONF_DEBUG_APPEND_INFO = 15;
    /**
     * Any post requests that don't include file uploads are automatically redirected to avoid back-button issues.
     */
    const CONF_POST_AUTO_REDIRECT_WO_FILES = 16;
    /**
     * All post requests are automatically redirected to avoid back-button issues.
     */
    const CONF_POST_AUTO_REDIRECT_ALL = 17;
    /**
     * Automatic post redirects are turned off.
     */
    const CONF_POST_AUTO_REDIRECT_OFF = 18;
    /**
     * Convert date to MySQL datetime (same formate as MySQL timestamp)
     */
    const DATE_MYSQL_DATETIME = 'Y-m-d H:i:s';
    /**
     * Convert date to MySQL date
     */
    const DATE_MYSQL_DATE = 'Y-m-d';
    /**
     * Allow integers.
     */
    const FILTER_INT = 1;
    /**
     * Allow positive or negative numbers with decimal points.
     */
    const FILTER_DOUBLE = 2;
    /**
     * Allow alphanumeric text.
     */
    const FILTER_ALPHANUM = 3;
    /**
     * Allow characters typically used in variable names.
     */
    const FILTER_VAR_NAME = 19;
    /**
     * Allow valid email addresses.
     */
    const FILTER_EMAIL = 4;
    /**
     * Allow valid URL's.
     */
    const FILTER_URL = 5;
    /**
     * Allow valid US zip codes.
     */
    const FILTER_US_ZIP = 10;
    /**
     * Allow valid US phone numbers.
     */
    const FILTER_US_PHONE = 11;
    /**
     * Allow a single line of text except for XML markup and danerous hackemes.
     */
    const FILTER_TEXT = 7;
    /**
     * Allow multiple lines of text except for XML markup and dangerous hackemes.
     */
    const FILTER_TEXT_MULTILINE = 9;
    /**
     * Allow all types of text except for dangerous hackemes.
     */
    const FILTER_XML = 6;
    /**
     * Allow everything under the sun.
     */
    const FILTER_ALL = 8;
    /**
     * Allow data that validates with custom regex (you must supply the regex in the settings array for the port.)
     */
    const FILTER_CUSTOM = 21;
    /**
     * Regex used for validating positive integers.
     */
    const REGEX_INT = '/^\d+$/';
    /**
     * Regex used for validating double numbers.
     */
    const REGEX_DOUBLE = '/^-?\d+(\.\d+)?$/';
    /**
     * Regex used for validating alphanumeric data (a - z, 0 - 9, case insensitive.)
     */
    const REGEX_ALPHANUM = '/^[a-zA-Z0-9]+$/';
    /**
     * Regex used for validating variable names (a - z, 0 - 9, '_', '-', case insensitive.)
     */
    const REGEX_VAR_NAME = '/^[-a-zA-Z0-9_]+$/';
    /**
     * Regex used for validating US phone numbers.
     */
    const REGEX_US_PHONE = '/^((1)?[2-9]\d{9})|(((1)?\([2-9]\d{2}\))|((1-)?[2-9]\d{2}-))\d{3}-\d{4}$/';
    /**
     * Regex used for validating US Zip codes.
     */
    const REGEX_US_ZIP = '/^\d{5}((-)?\d{4})?$/';
    /**
     * Regex used for validating basic text, free of HTML special characters.
     */
    const REGEX_TEXT = '/^[ \/a-zA-Z0-9.!@#$%&*:;?()_+=\'",\[\]-]+$/';
    /**
     * Regex used for validating basic multiline text, free of HTML special characters.
     */
    const REGEX_TEXT_MULTILINE = '/^[ \/a-zA-Z0-9.!@#$%&*:;?()_+=\'"\n\r,\[\]-]+$/';
    /**
     * Regex used for validating text that contains XML.
     */
    const REGEX_XML = '/^(.|\n)+$/';
    /**
     * Regex used for file names, which allows letters, numbers, whitespace, '.', and the underscore.
     */
    const REGEX_FILE_NAME = '/^[-a-zA-Z0-9_\s\.]+$/';
    /**
     * Regex used for URL's.
     */
    const REGEX_URL = '/^(http|https):\/\/(([-a-zA-Z0-9]+)\.)*([-a-zA-Z0-9]+)\.([a-z]{2,6})(\/([-a-zA-Z0-9#_.=?+&;:%])+)*$/';
    /**
     * Regex used for email addresses.
     */
    const REGEX_EMAIL = '/^[a-z0-9!#$%&*+=?^_`{|}~-]+(?:\.[a-z0-9!#$%&*+=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+([a-z]{2,6})$/';
    /**
     * Request for standard page output.
     */
    const REQUEST_STANDARD = 19;
    /**
     * Request for the output of one particular pipe.
     */
    const REQUEST_HTMLFRAG = 20;
    /**
     * Whitelist regex that Pith uses to allow links through automatic encoding
     */
    const WHITELIST_A_TAG = '/<a href="(((http|https):\/\/)|(mailto:[-a-zA-Z0-9#_.+]*@))(([-a-zA-Z0-9]+)\.)*([-a-zA-Z0-9]+)\.([a-z]{2,6})(\/([-a-zA-Z0-9#_.=?+&;:%])+)*">[ a-zA-Z0-9,)(\.\?!@-]+<\/a>/';
}
/**
 * Class containing all functions utilized by the Nephtali Framework.
 * @author Adam Richardson
 *
 */
class N
{
    /**
     * Output the value for debugging purposes.  Shorthand for calling exit(var_dump($value)).
     * @param mixed $debug_value
     * @return void
     */
    public static function _($debug_value)
    {
        exit(var_dump($debug_value));
    }
    /**
     * Provides access to config values.
     * @param mixed $names If an array of names, returns an array of config values.  If one name, returns that one value.
     * @return mixed
     */
    public static function config_get($names)
    {
        static $values = 'empty';

        if (is_array($names) && $values == 'empty')
        {
            $values = $names;
            return;
        }
        else
        {
            // if array, return array
            if (is_array($names)) return array_intersect_key($values, array_fill_keys($names, 0));
            // if one value, return one value
            return $values[$names];
        }
    }
    /**
     * FTP's a file to a server via curl.
     * @param string $local_file_path
     * @param string $file_name
     * @param string $ftp_domain
     * @param string $ftp_username
     * @param string $ftp_password
     */
    public static function curl_file_ftp_upload($local_file_path, $local_file_size, $file_name, $ftp_domain, $ftp_username, $ftp_password)
    {
        $fp = fopen($local_file_path, 'r');
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, 'ftp://'.urlencode($ftp_username).':'.urlencode($ftp_password).'@'.$ftp_domain.$file_name);
        curl_setopt($ch, CURLOPT_UPLOAD, 1);
        curl_setopt($ch, CURLOPT_INFILE, $fp);
        curl_setopt($ch, CURLOPT_INFILESIZE, $local_file_size);
        // needed so output isn't echo'd back
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_exec($ch);
        $error_no = curl_errno($ch);
        curl_close ($ch);
        if ($error_no != 0) {
            if (is_int($error_no))
            {
                throw new Exception('There was an error uploading the file \''.$file_name.'\'. CURL error number:'.$error_no);
            }
            else
            {
                throw new Exception('There was an error uploading the file, and the CURL error number was invalid, too.');
            }
        }
    }
    /**
     * Save a file upload using CURL to ftp to the site and upload the file.
     * @param <type> $post_key
     * @param <type> $file_name
     * @param <type> $ftp_domain
     * @param <type> $ftp_username
     * @param <type> $ftp_password
     */
    public static function curl_file_save_upload($post_key, $file_name, $ftp_domain, $ftp_username, $ftp_password)
    {
        self::curl_file_ftp_upload($local_file_path = $_FILES[$post_key]['tmp_name'], $local_file_size = $_FILES[$post_key]['size'], $file_name, $ftp_domain, $ftp_username, $ftp_password);
    }
    /**
     * Retrieves the contents of a url via CURL
     * @param string $url
     * @param int $connect_timeout
     * @param int $transaction_timeout
     * @return string
     */
    public static function curl_file_get_contents($url, $connect_timeout = 1, $transaction_timeout = 2)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
        curl_setopt($ch, CURLOPT_TIMEOUT, $transaction_timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $str = curl_exec($ch);
        $error_no = curl_errno($ch);
        curl_close ($ch);
        
        if($error_no != 0)
        {
            if(is_int($error_no))
            {
                throw new Exception('There was an error retreiving the string contents of the url \''.$url.'\'. CURL error number:'.$error_no);
            }
            else
            {
                throw new Exception('There was an error saving the url \''.$url.'\', and the CURL error number was invalid, too.');
            }
        }
        else
        {
            return $str;
        }
    }
    /**
     * Retrieves the entries (title, link, description, and updated fields) for an atom feed.
     * @param string $url
     * @param int $limit
     * @return array
     */
    public static function feed_atom($url, $limit = null)
    {
        $doc = new DOMDocument();
        $doc->loadXML(self::curl_file_get_contents($url));
        $rows = array();

        foreach ($doc->getElementsByTagName('entry') as $node)
        {
            if($limit > count($rows))
            {
                $rows[] = array (
                    'title'     => trim($node->getElementsByTagName('title')->item(0)->nodeValue),
                    'content'   => trim($node->getElementsByTagName('content')->item(0)->nodeValue),
                    'link'      => trim($node->getElementsByTagName('link')->item(0)->getAttribute('href')),
                    'updated'   => trim($node->getElementsByTagName('updated')->item(0)->nodeValue)
                );
            }
            else
            {
                break;
            }
        }

        return $rows;
    }
    /**
     * Retrieve the search results (title, url, description) for a Bing-based site query through the API.
     * @param string $site
     * @param string $app_id
     * @param string $query
     * @param int $limit
     * @return array
     */
    public static function feed_bing_search_site($site, $app_id, $query, $limit = 20)
    {
        $query = str_replace(' ', '+', $query);

        $url = "http://api.search.live.net/json.aspx?Appid=".urlencode($app_id)."&query=site:".urlencode($site)."+".urlencode($query)."&sources=web&web.count=".urlencode($limit);

        $json_results = json_decode(self::curl_file_get_contents($url), true);

        $results = $json_results['SearchResponse']['Web']['Results'];

        for ($i = 0; $i < count($results); $i++)
        {
            $results[$i] = array_change_key_case($results[$i]);
        }

        return $results;
    }
    /**
     * Retrieves the items (title, link, description, and updated fields) for an RSS feed.
     * @param string $url
     * @param int $limit
     * @return array
     */
    public static function feed_rss($url, $limit = null)
    {
        $doc = new DOMDocument();
        $doc->loadXML(self::curl_file_get_contents($url));
        $rows = array();

        foreach ($doc->getElementsByTagName('item') as $node)
        {
            if($limit > count($rows))
            {
                $rows[] = array (
                    'title' => trim($node->getElementsByTagName('title')->item(0)->nodeValue),
                    'description'  => trim($node->getElementsByTagName('description')->item(0)->nodeValue),
                    'link'  => trim($node->getElementsByTagName('link')->item(0)->nodeValue),
                    'pubDate'  => trim($node->getElementsByTagName('pubDate')->item(0)->nodeValue)
                );
            }
            else
            {
                break;
            }
        }

        return $rows;
    }
    /**
     * Retrieve the search results (title, url, clickurl, abstract, date, size) for a Yahoo! Boss-based site query through the API.
     * @param string $site
     * @param string $app_id
     * @param string $query
     * @param int $limit
     * @return array
     */
    public static function feed_yahoo_search_site($site, $app_id, $query, $limit = 20)
    {
        $url = 'http://boss.yahooapis.com/ysearch/web/v1/'.urlencode($query).'?appid='.urlencode($app_id).'&sites='.urlencode($site).'&count='.urlencode($limit);

        $json_results = json_decode(self::curl_file_get_contents($url), true);

        return $json_results['ysearchresponse']['resultset_web'];
    }
    /**
     * Returns the array of sections formed by a pipe, view, or data region.
     * @param string $markup
     * @param string $name
     * @param string $type
     * @return array
     */
    public static function markup_get_sections($markup, $type, $name = null)
    {
        if ($type != 'data' && count($sections = explode('<!--'.$type.':'.$name.'-->', $markup)) == 3)
        {
            return $sections;
        }
        else
        {
            if ($type == 'data')
            {
                if (count($sections = explode('<!--data-->', $markup)) == 3)
                {
                    return $sections;
                }
                else
                {
                    return array($markup);
                }
            }
            else
            {
                throw new Exception('The following '.$type.' could not be properly found in the markup: '.$name);
            }
        }
    }
    /**
     * Retrieve the settings for registered pipes.  With no argument, all pipes are retrieved.
     * @param mixed $names If an array of names is provided, an array of pipes is returned.  If one name is provided (string), then that one pipe is returned.  Default is an empty array, which leads to returning all registered pipes.
     * @return mixed
     */
    public static function pipe_get($names = array())
    {
        if (is_array($names)) return N::z_pipe_values('get', $names);

        return current(self::z_pipe_values($mode = 'get', array($names)));
    }
    /**
     * Register a pipe for the page.  If the $paths argument is passed in, pipes will only be registered if the path matches that of the current page.
     * @param string $name
     * @param string $function
     * @param array $settings
     * @param array $paths
     * @return void
     */
    public static function pipe_register($name, $function, array $settings = array(), $paths = array())
    {
        if (!$paths) return self::z_pipe_values('set', array('name' => $name, 'function' => $function, 'settings' => $settings));

        foreach ($paths as $path)
        {
            if (strpos($_SERVER['PHP_SELF'], $path) === 0)
            {
                return self::z_pipe_values('set', array('name' => $name, 'function' => $function, 'settings' => $settings));
            }
        }
    }
    /**
     * Register a pipe that conditionally runs any one of several functions depending upon the ports present on a particular request.
     * @param string $name
     * @param array $actions Associative array of the function names and the corresponding port signatures that should trigger the function (first valid signature is run.)
     * @param array $settings
     * @param array $paths
     * @return void
     */
    public static function pipe_register_action($name, array $actions, array $settings = array(), array $paths = array())
    {
        foreach ($actions as $function => $ports)
        {
            if (self::port_signature($ports))
            {
                return self::pipe_register($name, $function, $settings, $paths);
            }
        }

        self::pipe_register($name, $function = create_function('$markup', 'return \'\';'), $settings, $paths);
    }
    public static function pipe_register_form($name, array $rows, array $settings = array(), array $paths = array())
    {
        // coming soon when 5.3 is supported
    }
    /**
     * Retrieve an array of the port settings for the ports named in the array.  If no second argument is provided, by default the value of the port is returned.
     * @param mixed $names If provided an array of names, an array of port values is returned.  If provided one name, one port value is returned.
     * @param string $key Key of the port array element to retrieve (defaults to 'value'.)
     * @return array
     */
    public static function port_get($names = array(), $key = 'value')
    {
        if (is_array($names))
        {
            $ports = self::z_port_values('get', $names);

            // remove internal ports if it's a request for all ports
            // this should eventually be removed by refactoring so these ports are validated without registering
            if (!count($names))
            {
                unset($ports['nports']);
                unset($ports['npipe']);
                unset($ports['nmode']);
            }

            foreach ($ports as $name => $port)
            {
                if (!$port['valid']) throw new Exception('You cannot retrieve the value of an invalid port with ports().  Check to make sure you are properly checking port validity in your pipe.');
                $ports[$name] = $port[$key];
            }

            return $ports;
        }
        else
        {
            $port = current(self::z_port_values('get', array($names)));

            if (!$port['valid']) throw new Exception('You cannot retrieve the value of an invalid port with ports().  Check to make sure you are properly checking port validity in your pipe.');

            return $port[$key];
        }
    }
    /**
     * Register a port for use within pipe functions.
     * @param string $name
     * @param mixed $value
     * @param array $settings Settings include min_length, max_length, filter, regex, allow_null, allow_array, encrypted, error_messages, formatted_name.
     * @param array $paths
     * @return void
     */
    public static function port_register($name, $value, array $settings = array(), array $paths = array())
    {
        if (!$paths) return self::z_port_save(array('name' => $name, 'value' => $value, 'settings' => $settings, 'type' => 'char_data'));
    
        foreach ($paths as $path)
        {
            if (strpos($_SERVER['PHP_SELF'], $path) === 0)
            {
                return self::z_port_save(array('name' => $name, 'value' => $value, 'settings' => $settings, 'type' => 'char_data'));
            }
        }
    }
    public static function port_register_file($name, $value, array $settings = array(), array $paths = array())
    {
        if (!$paths) return self::z_port_save(array('name' => $name, 'value' => $value, 'settings' => $settings, 'type' => 'file'));

        foreach ($paths as $path)
        {
            if (strpos($_SERVER['PHP_SELF'], $path) === 0)
            {
                return self::z_port_save('set', array('name' => $name, 'value' => $value, 'settings' => $settings, 'type' => 'file'));
            }
        }
    }
    /**
     * Determine if the array of ports are set on the given page request.
     * @param array $ports Array of port names to check.
     * @return bool
     */
    public static function port_signature(array $ports)
    {
        foreach (self::z_port_values('get', $names = $ports) as $port)
        {
            if (!$port['signature']) return false;
        }

        return true;
    }
    /**
     * Validate the group of ports named in the array.
     * @param array $ports
     * @return array $rows Array of associative arrays contained containing port names and error messages (array is empty if all ports were valid.)
     */
    public static function port_validate(array $ports = array())
    {
        $rows = array();
        $retrieved_ports = self::z_port_values('get', $names = $ports);

        // remove internal ports if it's a request for all ports
        // this should eventually be refactored by validating these ports without registering
        if (!count($ports))
        {
            unset($retrieved_ports['nports']);
            unset($retrieved_ports['npipe']);
            unset($retrieved_ports['nmode']);
        }

        foreach ($retrieved_ports as $name => $port)
        {
            if (!is_null($port['error_message']))
                $rows[$port['name']] = array('message' => $port['error_message']);
        }

        return $rows;
    }
    /**
     * Databinds associative array row to the markup according to the mappings.
     * @param string $markup
     * @param array $row
     * @param array $whitelists
     * @return string
     */
    public static function row_databind($markup, $row, $whitelists)
    {
        foreach ($row as $key => $value)
        {
            $markup = str_replace('{'.$key.'}', self::str_encode($value, $whitelists[$key]), $markup);
        }

        return $markup;
    }
    /**
     * Formats the date fields contained within a row (e.g., a key => value associative array) to and from the formatting options found at http://www.php.net/manual/en/function.date.php.
     * @param array $rows Array of associative array rows that contains the date fields to be formatted.
     * @param array $date_fields Associative array containing field names as keys and the corresponding formatting string (some predefined constants are available under NC::DATE_XXX) for the php date() function.  Additionally, PHP also has predefined consants available: http://www.php.net/manual/en/class.datetime.php#datetime.constants.types
     * @return array
     */
    public static function row_format_dates(array $row, array $date_fields)
    {
        foreach ($date_fields as $key => $value)
        {
            $row[$key] = date($value, strtotime($row[$key]));
        }

        return $row;
    }
    /**
     * Encrypts value using Rijndael 256
     * @param string|int $value
     * @param string|int $key
     * @param string|int $salt
     * @return string
     */
    public static function security_encrypt($value, $key = null, $salt = null)
    {
        if (is_null($key))
            $key = self::config_get('encryption_key');
            
        if (is_null($salt))
            $salt = self::config_get('encryption_salt');
        
        $join = create_function('$value1,$value2', 'return base64_encode($value1).\'|\'.base64_encode($value2);');

        $hash = create_function('$value,$salt', 'return hash_hmac(\'sha256\', $value, $key, true);');
    
        $hashedKey = $hash($key, $salt);
    
        // time serves as nonce
        $mt = microtime(true);
    
        // concat base64'd mt and value
        $dataAndMessage = $join($mt, $value);
    
        // get mac for data and message
        $mac = $hash($dataAndMessage, $hashedKey);
    
        $dataAndMessageAndMac = $join($dataAndMessage, $mac);
    
        $cipherText = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $hashedKey, $dataAndMessageAndMac, 'cbc', $hash($mt, $salt));
    
        return $join($mt, $cipherText);
    }
    /**
     * Decrypts strings encrypted with the encrypt() function.
     * @param string $value
     * @param string|int $key
     * @param string|int $salt
     * @return string|int
     */
    public static function security_decrypt($value, $key = null, $salt = null)
    {
        if (is_null($key))
            $key = self::config_get('encryption_key');
            
        if (is_null($salt))
            $salt = self::config_get('encryption_salt');
        
        $break = create_function('$string', '$elements = explode(\'|\', $string);$elements[0] = base64_decode($elements[0]);$elements[1] = base64_decode($elements[1]);return $elements;');
    
        $hash = create_function('$value,$salt', 'return hash_hmac(\'sha256\', $value, $key, true);');
    
        // sha256_d converts key to 128 bit result in binary
        $hashedKey = $hash($key, $salt);
    
        list($mt, $cipherText) = $break($value);
    
        $dataAndMessageAndMac = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $hashedKey, $cipherText, 'cbc', $hash($mt, $salt));
    
        list($dataAndMessage, $mac) = $break($dataAndMessageAndMac);
    
        $hmacResult = $hash($dataAndMessage, $hashedKey);
    
        if($hmacResult != trim($mac))
        {
            throw new Exception('The encrypted value appears to be corrupted.');
        }
        else
        {
            list($mt, $message) = $break($dataAndMessage);
            return $message;
        }
    }
    /**
     * Start session by checking to see if one is already started before calling.
     * @return void
     */
    public static function session_start()
    {
        if (isset($_SESSION)) return;

        session_start();
    }
    /**
     * Creates and returns a connection handle for data sources (i.e., queries that read rows of a table, such as select queries.)
     * @param $dsn
     * @param $username
     * @param $password
     * @return unknown_type
     */
    public static function sql_source_connect($dsn = null, $username = null, $password = null)
    {
        if (is_null($dsn))
            $dsn = self::config_get('sql_source_dsn');
    
        if (is_null($username))
            $username = self::config_get('sql_source_username');
    
        if (is_null($password))
            $password = self::config_get('sql_source_password');
    
        $dbh = new PDO($dsn, $username, $password);
        $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
        return $dbh;
    }
    /**
     * Creates and returns a connection handle for data actions (i.e., queries that alter rows of the table, such as insert, update, and delete.)
     * @param $dsn
     * @param $username
     * @param $password
     * @return unknown_type
     */
    public static function sql_action_connect($dsn = null, $username = null, $password = null)
    {
        if (is_null($dsn))
            $dsn = self::config_get('sql_action_dsn');
    
        if (is_null($username))
            $username = self::config_get('sql_action_username');
    
        if (is_null($password))
            $password = self::config_get('sql_action_password');
    
        $dbh = new PDO($dsn, $username, $password);
        $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
        return $dbh;
    }
    /**
     * Performs read-only ('SELECT') queries on tables and returns results.
     * @param array $query
     * @param $inputs
     * @param $dbh
     * @return array
     */
    public static function sql_source_query($query, array $inputs = array(), $dbh = null)
    {
        if (is_null($dbh))
            $dbh = self::sql_source_connect();
    
        $sth = $dbh->prepare($query);
    
        if (!$sth->execute($inputs))
        {
            throw new Exception('The source query could not be completed.');
        }
    
        return $sth->fetchAll(PDO::FETCH_ASSOC);
    }
    /**
     * Performs queries that alter table rows, such as 'INSERT', 'UPDATE', and 'DELETE' queries.
     * @param array $query
     * @param $inputs
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_query($query, array $inputs, $dbh = null)
    {
        if (is_null($dbh))
            $dbh = self::sql_action_connect();
    
        $sth = $dbh->prepare($query);
    
        if (!$sth->execute($inputs))
        {
            throw new Exception('The action query could not be completed.');
        }
    
        if(strtolower(substr(trim($query), 0, 6)) == 'insert')
            return $dbh->lastInsertId();
    
        return;
    }
    /**
     * Convenience function for deleting a row from a table by it's id (must follow the convention that the table primary key is named 'id'.)
     * @param $table_name
     * @param $id
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_delete($table_name, $id, $dbh = null)
    {
        return self::sql_action_query("DELETE FROM $table_name WHERE id = ?", array($id), $dbh);
    }
    /**
     * Convenience function for inserting an associative array row into a table (array keys must match the column names of the table.)
     * @param array $table_name
     * @param $inputs
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_insert($table_name, array $inputs, $dbh = null)
    {
        $keys = array_keys($inputs);
        $fields = implode(',', $keys);
        $place_holders = implode(',', array_map(create_function('$v', 'return \':\'.$v;'), $keys));
        return self::sql_action_query("INSERT INTO $table_name ($fields) VALUES ($place_holders)", $inputs, $dbh);
    }
    /**
     * Convenience function for updating a table row with values from an associative array (array keys must match the column names of the table.)
     * @param array $table_name
     * @param $inputs
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_update($table_name, array $inputs, $dbh = null)
    {
        if (!isset($inputs['id']))
            throw new Exception('The action_update method requires that the table your updating follow the convention that it has a primary key called "id".  No key was found that matched, so try using the action_query() function to perform your update.');

        $keys = array_keys($inputs);
        unset($keys[array_search('id', $keys)]);
        $update_values = implode(',', array_map(create_function('$v', 'return $v.\' = :\'.$v;'), $keys));
        return self::sql_action_query("UPDATE $table_name SET $update_values WHERE id = :id", $inputs, $dbh);
    }
    /**
     * Wrapper for PHP's str_replace that encodes the replaced values and provides the opportunity to allow string whitelists that shouldn't be encoded.
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @param array $whitelist
     * @return string
     */
    public static function str_replace($search, $replace, $subject, $whitelist = array(), $charset = 'UTF-8')
    {
        return str_replace($search, self::str_encode($replace, $whitelist), $subject);
    }
    /**
     * HTML-encodes output and allows whitelists of values that should be let through without encoding.
     * @param string $string
     * @param array $allowedHTML
     * @return string
     */
    public static function str_encode($string, $allowed_html = array(), $charset = 'UTF-8')
    {
        // use htmlspecialchars because it only does the 5 most important chars, and doesn't mess them up
        $string = htmlspecialchars($string, ENT_QUOTES, $charset);
    
        if (count($allowed_html))
        {
            foreach($allowed_html as $tag)
            {
                if ((strpos($tag, '/') == 0) && (strrpos($tag, '/') == (strlen($tag)-1)))
                {
                    // Save entitied version of regex
                    // This should be acceptable because <, >, &, ', ", don't have special meaning in regexes
                    $entitied_regex = htmlspecialchars($tag, ENT_QUOTES, $charset);

                    // Tag represents a regular expression of the tag
                    $string = preg_replace_callback($entitied_regex, create_function('$matches','return html_entity_decode($matches[0]);'), $string);
                }
                else
                {
                    // Save entitied version of tag, then look for occurances of it in string and replace them
                    $entitied_tag = htmlspecialchars($tag, ENT_QUOTES, $charset);
                    $string = str_replace($entitied_tag, $tag, $string);
                }
            }
        }
    
        return $string;
    }
    /**
     * Immediately redirects from the current page to the URL.
     * @param string $url
     * @return void
     */
    public static function url_redirect($url)
    {
        ob_clean();
        header('Location: '.$url);
        exit();
    }
    public static function url_get_current()
    {
        if (isset($_SERVER['SCRIPT_URI']))
        {
            $url = $_SERVER['SCRIPT_URI'];

            if (count($_GET))
            {
                $url .= '?';
                foreach ($_GET as $key=>$value)
                {
                    $url .= $key.'='.$value.'&';
                }
            }

            return $url;
        }

        $url = '';
        $port = '';

        if (isset($_SERVER['HTTP_HOST']))
        {
            $colon = strpos($_SERVER['HTTP_HOST'], ':');

            if ($colon === false)
            {
                if (isset($_SERVER['SERVER_PORT']))
                {
                    $port = ':' . $_SERVER['SERVER_PORT'];
                }

                $url = $_SERVER['HTTP_HOST'];

            }
            else
            {
                $url = substr($_SERVER['HTTP_HOST'], 0, $colon);
                $port = substr($_SERVER['HTTP_HOST'], $colon);
            }
        }
        elseif(isset($_SERVER['SERVER_NAME']))
        {
            $url = $_SERVER['SERVER_NAME'];
            if (isset($_SERVER['SERVER_PORT']))
            {
                $port = ':' . $_SERVER['SERVER_PORT'];
            }
        }

        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
        {
            $url = 'https://' . $url;
            if ($port == ':443')
            {
                $port = '';
            }
        }
        else
        {
            $url = 'http://' . $url;
            if ($port == ':80')
            {
                $port = '';
            }
        }

        $url .= $port;

        if (isset($_SERVER['REQUEST_URI']))
        {
            $url .= $_SERVER['REQUEST_URI'];
        }
        else
        {
            throw Exception('We could not retrieve the URL for this page, so you\'ll have to turn off post reloading in nconfig.');
        }

        if (count($_GET))
        {
            $url .= '?';
            foreach ($_GET as $key=>$value)
            {
                $url .= $key.'='.$value.'&';
            }
        }

        return $url;
    }
    /**
     * Databinds rows of associative arrays to the markup contained within the <!--data--> region of the pipe markup.
     * @param $markup
     * @param $rows
     * @param $settings
     * @return string
     */
    public static function view_databind($markup, $rows, array $settings = array())
    {
        if (count($regions = self::markup_get_sections($markup, $type = 'data')) == 1)
        {
            return $markup;
        }

        $data_markup = $regions[1];

        $regions[1] = '';

        foreach ($rows as $row)
        {
            $row_markup = $data_markup;

            foreach ($settings['functions'] as $func)
            {
                $row_markup = $func($data_markup, $row);
            }

            $regions[1] .= self::row_databind($row_markup, $row, $settings['whitelists']);
        }

        return implode('', $regions);
    }
    /**
     * Returns the array of immutable Nephtali values named in the arugment.
     * @param array $names
     * @return array
     */
    public static function value_get(array $names)
    {
        return self::z_values($mode = 'get', $arr = $names);
    }
    /**
     * Saves a value to be databound to page (values are immutable, so get them right the first time :)
     * @param $name
     * @param $value
     * @return unknown_type
     */
    public static function value_register($name, $value)
    {
        self::z_values($mode = 'set', $arr = array('name' => $name, 'value' => $value));
    }
    /**
     * Returns view markup after any required databinding.
     * @param string $name
     * @param string $markup
     * @param array $rows
     * @param array $settings
     * @return string
     */
    public static function view_render($name, $markup, $rows = array(), array $settings = array())
    {
        $markup_sections = self::markup_get_sections($markup, $type = 'view', $name);

        if (count($markup_sections) != 3)
        {
            throw new Exception("We could not find the markup for the view '$name'.  Please make sure you have view comments in place for each of the views your pipes expect.");
        }

        return self::view_databind($markup_sections[1], $rows, $settings);
    }
    /**
     * Function primes config() function so subsequent calls retrieve the value associated with the argument key.
     * @param array $mode
     * @param array $dev
     * @param array $test
     * @param $prod
     * @return void
     */
    public static function z_config_save($mode, array $dev, array $test, array $prod)
    {
    	$defaults = array(
    	    'mode'						=> 'dev',
    	    'error_handling'            => NC::CONF_ERRORS_SUPPRESS,
    		'error_page'                => 'error.html',
    		'encryption_key'            => null,
    		'encryption_salt'           => null,
    		'upload_directory'          => '',
    		'upload_ftp_url'            => null,
    		'sql_action_dsn'            => null,
    		'sql_action_username'       => null,
    		'sql_action_password'       => null,
    		'sql_datasource_dsn'        => null,
    		'sql_datasource_username'   => null,
    		'sql_datasource_password'   => null,
            'post_request_handling'     => NC::CONF_POST_AUTO_REDIRECT_WO_FILES
        );
        
        if ($mode == 'dev')
        {
            $s = $dev;
            $s['mode'] = 'dev';
        }
        elseif ($mode == 'test')
        {
            $s = $test;
            $s['mode'] = 'test';
        }
        elseif ($mode == 'prod')
        {
            $s = $prod;
            $s['mode'] = 'prod';
        }
        else
        {
            throw new Exception('The mode must be one of the following strings: dev, test, prod.');
        }
    	
    	self::config_get(array_merge($defaults, $s));
    }
    public static function z_post_process_request()
    {
        if ((count($_POST) > 0) && (count($_FILES) < 1) && (N::config_get('post_request_handling') == self::CONF_POST_AUTO_REDIRECT_WO_FILES))
        {
            self::z_post_save_vars_to_session();
            self::z_post_set_redirect_flag();
            self::url_redirect($url= self::url_get_current());
        }
        else if((count($_POST) > 0) && (count($_FILES) > 0) && (N::config_get('post_request_handling') == N::AUTO_REDIRECT_ALL_POSTS))
        {
            /*
             * still have to add, don't do redirect for now
             *
             */
        }
        else if($_COOKIE['nephtali_post_redirect'] == 1)
        {
            self::session_start();

            if (isset($_SESSION['nephtali_post_vars']))
            {
                self::z_post_create_vars_from_session();
            }

            self::z_post_unset_redirect_flag();
        }
    }
    public static function z_post_save_vars_to_session()
    {
        self::session_start();
        
        foreach ($_POST as $key=>$value)
        {
            $_SESSION['nephtali_post_vars'][$key] = $value;
            unset($_POST[$key]);
        }
    }
    public static function z_post_create_vars_from_session()
    {
        self::session_start();

        foreach ($_SESSION['nephtali_post_vars'] as $key=>$value)
        {
            $_POST[$key] = $value;
            unset($_SESSION['nephtali_post_vars'][$key]);
        }
    }
    public static function z_post_set_redirect_flag()
    {
        setCookie('nephtali_post_redirect', 1);
    }
    public static function z_post_unset_redirect_flag()
    {
        setCookie('nephtali_post_redirect', 0);
    }
    /**
     * File exists function that takes into account include path.
     *
     * @param string $file
     * @return bool
     */
    public static function z_file_exists($file)
    {
        $paths = explode(PATH_SEPARATOR, get_include_path());
    
        foreach ($paths as $path)
        {
            $path = $path.'/'.$file;
    
            if (file_exists($path))
            {
                return true;
            }
        }
    
        return false;
    }
    /**
     * Sets error handling mode for the page.
     *
     * @param int $mode
     */
    function z_set_error_handling($mode)
    {
        // E_STRICT will be part of PHP 6, so might as well start now
        error_reporting(E_ALL | E_STRICT);
    
        // Make things exceptions instead of errors (when possible)
        set_error_handler(array('N', 'z_error_handler'));
    
        if ($mode == 'dev' || $mode == 'test')
        {
            ini_set('display_errors', '1');
            set_exception_handler(array('N', 'z_dev_handler'));
        }
        else
        {
            ini_set('display_errors', '0');
            set_exception_handler(array('N', 'z_prod_handler'));
        }
    }
    /**
     * Handles errors by throwing them as exceptions with the underlying info contained in the message.
     *
     * @param int $errno
     * @param string $errstr
     * @param string $errfile
     * @param int $errline
     */
    public static function z_error_handler($errno, $errstr, $errfile, $errline)
    {
        // must take into account error suppressor (@) and not do anything with them (they equal 0)
        // http://framework.zend.com/issues/browse/ZF-3829
        if ($errno < 1)
        {
            $error_msg = "<dl><dt>Error Message:</dt><dd>$errstr</dd><dt>File:</dt><dd>$errfile</dd><dt>Line:</dt><dd>$errline</dd></dl>";
            throw new Exception($error_msg);
        }
    }
    /**
     * Handles exceptions not caught by the page when the application is in prod mode.
     * Any output is erased and the user is redirected to the default error page set in nconfig.php.
     * @param void $exception
     */
    public static function z_prod_handler($exception)
    {
        self::url_redirect(N::config_get('error_page'));
    }
    /**
     * Handles exceptions not caught by the page when the application is in dev mode.
     * Details of the exception and a backtrace of the code that led to the exception are output to the screen.
     * @param void $exception
     */
    public static function z_dev_handler($exception)
    {
        // Get rid of any output that's already buffered
        ob_end_clean();
    
        ?>
        <html>
        <head>
        <title>Debug output for unhandled exception</title>
        </head>
        <body>
        <h1>Debug output for unhandled exception</h1>
        <?php echo $exception->getMessage(); ?><br />
        <h2>Debug Backtrace</h2>
        <pre><?php echo self::str_encode(print_r(debug_backtrace(), true)); ?></pre>
        </body>
        </html>
        <?php
        die();
    }
    
    public static function z_pipe_values($mode, array $arr)
    {
        static $pipes = array();
        
        // retrieve the pipes in the array
        if ($mode == 'get')
        {
            if (count($arr) == 1)
            {
                if (!isset($pipes[$arr[0]]))
                    throw new Exception('There is no registered pipe called "'.$arr[0].'" for this page.');
                    
                return array($arr[0] => $pipes[$arr[0]]);
            }
            elseif (count($arr) > 1)
            {
                return array_intersect_key($pipes, array_fill_keys($arr, 0));   
            }
            else
            {
                return $pipes;   
            }
        }
        // set the pipe given in the array
        else
        {
            if (!isset($pipes[$arr['name']]))
            {
                $defaults = array('required_pipes' => array(), 'has_output' => true);
                $settings = array_merge($defaults, $arr['settings']);
                $pipes[$arr['name']] = array('name' => $arr['name'], 'function' => $arr['function'], 'settings' => $settings);
                return;
            }
            else
            {
                throw new Exception('There is already a pipe called "'.$arr['name'].'" that has been registered for this page.');
            }
        }
    }
    public static function z_port_save($arr)
    {
        if ($arr['type'] == 'char_data')
        {
            // used for error output
            $formatted_name = str_replace('_', ' ', ucfirst($arr['name']));

            $defaults = array(
                'min_length'         => 1,
                'max_length'         => 10,
                'min_value'		     => 1,
                'max_value'			 => 9999999999,
                'filter'             => NC::FILTER_INT,
                'regex'				 => null,
                'encrypted'          => false,
                'allow_null'         => false,
                'whitelist'          => array(),
                'allow_array'        => false,
                'formatted_name'     => $formatted_name,
                'error_messages'     => array()
            );

            $settings = array_merge($defaults, $arr['settings']);

            $whitelist = "'".$settings['formatted_name']."' only accepts the following values:  ".implode(',', $settings['whitelist']).".";

            switch ($settings['filter'])
            {
                case NC::FILTER_INT:
                    $error_message = "'{$settings['formatted_name']}' only accepts numbers.";
                    break;
                case NC::FILTER_DOUBLE:
                    $error_message = "'{$settings['formatted_name']}' only accepts positive or negative numbers with or without decemals.";
                    break;
                case NC::FILTER_ALPHANUM:
                    $error_message = "'{$settings['formatted_name']}' only accepts numbers and letters as input.";
                    break;
                case NC::FILTER_VAR_NAME:
                    $error_message = "'{$settings['formatted_name']}' only accepts numbers, letters, underscores, and dashes as input.";
                    break;
                case NC::FILTER_US_PHONE:
                    $error_message = "'{$settings['formatted_name']}' only accepts valid US phone numbers as input.";
                    break;
                case NC::FILTER_US_ZIP:
                    $error_message = "'{$settings['formatted_name']}' only accepts valid US zip codes as input.";
                    break;
                case NC::FILTER_URL:
                    $error_message = "'{$settings['formatted_name']}' only accepts valid URLs as input.";
                    break;
                case NC::FILTER_EMAIL:
                    $error_message = "'{$settings['formatted_name']}' only accepts valid email addresses as input.";
                    break;
                case NC::FILTER_TEXT:
                    $error_message = "'{$settings['formatted_name']}' only accepts a single line of basic text as input (no HTML input allowed.)";
                    break;
                case NC::FILTER_TEXT_MULTILINE:
                    $error_message = "'{$settings['formatted_name']}' only accepts basic text as input (no HTML input allowed.)";
                    break;
                case NC::FILTER_XML:
                    $error_message = "'{$settings['formatted_name']}' only accepts text and XML as input.";
                    break;
                case NC::FILTER_CUSTOM:
                    $error_message = "'{$settings['formatted_name']}' is not valid data.";
                default:
                    $error_message = "The data received was invalid.";
            }

            $default_messages = array(
                'min_length'		 => "'{$settings['formatted_name']}' has a minimum length of  {$settings['min_length']} character(s).",
                'max_length'		 => "'{$settings['formatted_name']}' has a maximum length of  {$settings['max_length']} character(s).",
                'allow_null'		 => "'{$settings['formatted_name']}' is an expected value, and we did not recieve it.",
                'whitelist'			 => $whitelist,
                'allow_array'		 => "'{$settings['formatted_name']}' does not allow arrays of data.",
                'filter'             => $error_message
            );

            if ($settings['min_length'] == 1) $default_messages['min_length'] = "'{$settings['formatted_name']}' is required and cannot be left blank.";

            $settings['error_messages'] = array_merge($default_messages, $settings['error_messages']);

            $arr = self::z_port_validate_chardata(array('name' => $arr['name'], 'formatted_name' => $formatted_name, 'value' => $arr['value'], 'settings' => $settings));
            self::z_port_values($mode = 'set', $arr);
            return;
        }
        elseif ($arr['type'] == 'file')
        {
            // used for error output
            $formatted_name = str_replace('_', ' ', ucfirst($arr['name']));

            $defaults = array(
                'max_bytes'          => 1048576,
                'required'           => true,
                'extensions'         => array('jpg', 'jpeg', 'gif'),
                'ftp_username'       => null,
                'ftp_password'       => null,
                'ftp_domain'         => null,
                'formatted_name'     => $formatted_name
            );

            $settings = array_merge($defaults, $arr['settings']);

            $friendly_bytes = self::z_bytes_friendly($settings['min_length']);
            $extensions = "'".$settings['formatted_name']."' only accepts files with the following extensions:  ".implode(',', $settings['extensions']).".";

            $default_messages = array(
                'max_bytes'          => "'{$settings['formatted_name']}' has a maximum size of $freindly_bytes.",
                'required'           => "'{$settings['formatted_name']}' is required.",
                'extensions'         => $extensions,
                'is_upload'          => "'{$settings['formatted_name']}' does not appear to be an uploaded file."
            );

            $settings['error_messages'] = array_merge($default_messages, $settings['error_messages']);

            $arr = self::z_port_validate_file(array('name' => $arr['name'], 'formatted_name' => $formatted_name, 'value' => $arr['value'], 'settings' => $settings));
            self::z_port_values($mode = 'set', $arr);
            return;
        }
        else
        {
            throw new Exception('The type for the port was not recognized.');
        }
    }
    public static function z_port_values($mode, array $arr)
    {
        static $ports = array();

        // retrieve the pipes in the array
        if ($mode == 'get')
        {
            if (count($arr))
            {
                return array_intersect_key($ports, array_fill_keys($arr, 0));
            }
            else
            {
                return $ports;   
            }
        }
        // set the port given in the array
        else
        {
            if (!isset($ports[$arr['name']]))
            {                   
                return $ports[$arr['name']] = $arr;
            }
            else
            {
                throw new Exception('A port called "'.$arr['name'].'" has already been registered for this page.');
            }
        }   
    }
    public static function z_bytes_compute($val)
    {
        $val = trim($val);

        switch ($last = strtolower(substr($val, -1))) {
            // The 'G' modifier is available since PHP 5.1.0
            case 'g':
                return substr($val, 0, strlen($val) - 1)*1073741824;
            case 'm':
                return substr($val, 0, strlen($val) - 1)*1048576;
            case 'k':
                return substr($val, 0, strlen($val) - 1)*1024;
            default:
                return $val;
        }
    }
    public static function z_bytes_friendly($val)
    {
        if ($val < 1024)
        {
            return $val.' bytes';
        }
        if ($val < 1048576)
        {
            return ($val/1024).'KB';
        }
        if ($val < 1073741824)
        {
            return ($val/1048576).'MB';
        }

        return ($val/1073741824).'GB';
    }
    public static function z_values($mode, $arr = array())
    {
        static $values = array();

        // retrieve the pipes in the array
        if ($mode == 'get')
        {
            if (count($arr) == 1)
            {
                if (!isset($values[$arr[0]]))
                    throw new Exception('There is no registered value called "'.$arr[0].'" for this page.');

                return array($values[$arr[0]]);
            }
            elseif (count($arr) > 1)
            {
                return array_intersect_key($values, array_fill_keys($arr, 0));
            }
            else
            {
                return $values;
            }
        }
        else
        {
            if (isset($values[$arr['name']]))
            {
                throw new Exception('A value called "'.$arr['name'].'" has already been registered for this page.');
            }

            $values[$arr['name']] = $arr['value'];
        }
    }
    public static function z_port_validate_file(array $arr)
    {
        if ($arr['value']['size'] < 1)
        {
            if ($arr['settings']['required']) return self::z_port_declare_invalid($arr, $failed_test = 'required');

            return self::z_port_declare_valid($arr);
        }

        if (!is_uploaded_file($arr['value']['tmp_name']))
        {
            return self::z_port_declare_invalid($arr, $failed_test = 'is_upload');
        }

        if ($arr['value']['size'] > $arr['settings']['max_bytes'])
        {
            return self::z_port_declare_invalid($arr, $failed_test = 'max_bytes');
        }

        $info = pathinfo($arr['value']['name']);

        if (!in_array($info['extension'], $arr['settings']['extensions']))
        {
            return self::z_port_declare_invalid($arr, $failed_test = 'extensions');
        }

        return self::z_port_declare_valid($arr);
    }
    public static function z_port_validate_chardata(array $arr)
    {   
        // check for NULLs
        if (is_null($arr['value']))
        {
            if ($arr['settings']['allow_null'])
            {
                return self::z_port_declare_valid($arr);
            }
            else
            {
                return self::z_port_declare_invalid($arr, $failed_test = 'allow_null');
            }
        }
        
        // check for arrays
        if (is_array($arr['value']))
        {
            // take care of this later.
        }
        else
        {
            // check if the value has been encrypted, and if so, decrypt
            if($arr['settings']['encrypted'])
            {
                $arr['value'] = self::security_decrypt($arr['value']);
            }
            // whitelist validation
            if(count($arr['settings']['whitelist']) > 0)
            {
                if (!in_array($arr['value'], $arr['settings']['whitelist']))
                {
                    return self::z_port_declare_invalid($arr, $failed_test = 'whitelist');
                }
            
                return self::z_port_declare_valid($arr);
            } 
            // if value is '' and minLength is 0, don't wast processing
            if ($arr['value'] == '' && $arr['settings']['min_length'] == 0)
            {
                return self::z_port_declare_valid($arr);
            }
            // check max length
            if(strlen($arr['value']) > $arr['settings']['max_length'])
            {
                return self::z_port_declare_invalid($arr, $failed_test = 'max_length');
            }
            // check min length
            if(strlen($arr['value']) < $arr['settings']['min_length'])
            {
                return self::z_port_declare_invalid($arr, $failed_test = 'min_length');
            }
            // filter
            switch ($arr['settings']['filter'])
            {
                case NC::FILTER_INT:
                    if (!preg_match(NC::REGEX_INT, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_DOUBLE:
                    if (!preg_match(NC::REGEX_DOUBLE, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_ALPHANUM:
                    if (!preg_match(NC::REGEX_ALPHANUM, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_VAR_NAME:
                    if (!preg_match(NC::REGEX_VAR_NAME, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_US_PHONE:
                    if (!preg_match(NC::REGEX_US_PHONE, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_US_ZIP:
                    if (!preg_match(NC::REGEX_US_ZIP, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_URL:
                    if (!preg_match(NC::REGEX_URL, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_EMAIL:
                    if (!preg_match(NC::REGEX_EMAIL, $arr['value']))
                    {
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');
                    }
                    break;
                case NC::FILTER_TEXT:
                    if (!preg_match(NC::REGEX_TEXT, $arr['value']) || self::z_port_contains_xss($arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_TEXT_MULTILINE:
                    if (!preg_match(NC::REGEX_TEXT_MULTILINE, $arr['value']) || self::z_port_contains_xss($arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_XML:
                    if (!preg_match(NC::REGEX_XML, $arr['value']) || self::z_port_contains_xss($arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_ALL:
                    break;
                case NC::FILTER_CUSTOM:
                    if (!preg_match($arr['settings']['regex'], $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;     
                default:
                    throw new Exception('The filter supplied for the port "'.$arr['name'].'" is not valid.');
           }

            return self::z_port_declare_valid($arr);
        }			   
    }
    public static function z_port_declare_valid(array $arr)
    {
        $arr['valid'] = true;
        $arr['signature'] = true;
        $arr['error_message'] = null;
        return $arr;
    }
    public static function z_port_declare_invalid(array $arr, $failed_test)
    {
        $arr['error_message'] = $arr['settings']['error_messages'][$failed_test];
        $arr['valid'] = false;
        ($failed_test == 'allow_null')?($arr['signature'] = false):($arr['signature'] = true);
        return $arr;
    }
    public static function z_port_contains_xss($value)
    {
		$xss_hackemes = array(
			'%3c', // <
			'&lt', // <
			'pa==',// <
			'&gt', // >
			'%3E', // >
			'pg==',// >
			'&quo',// "
			'%22', // "
			'ig==',// "
			'#27', // '
			'jw==',// '
			'&#',  // generally not needed
			'\x',  // generally not needed
			'\u',  // generally not needed
			'javascript',
			'onsubmit',
			'onabort',
			'onblur',
			'onchange',
			'onfocus',
			'onmouse',
			'onload',
			'onreset',
			'onselect',
			'onunload',
			'onerror',
			'onclick',
			'<script'
						);

		$lc_value = strtolower($value);

		foreach($xss_hackemes as $hackeme)
		{
			if(strpos($lc_value, $hackeme)===false) {
				continue;
			} else {
				return true;
			}
		}

		return false;
    }
    public static function z_pipe_process_registered($markup)
    {
        /**
         * Get configuration set up.
         */
        require('nconfig.php');
        /**
         * Configure environment with config settings.
         */
        N::z_set_error_handling(N::config_get('mode'));
        /**
         * Register pipes in npipe.php (authentication, etc.)
         */
        require('nregister.php');
        /**
         * Register code-behind pipes and ports, if present
         */
        if (self::z_file_exists('nsite'.$_SERVER['PHP_SELF']))
        {
            require('nsite'.$_SERVER['PHP_SELF']);
        }

        $nmode_whitelist = array('htmlfrag','validate');

        self::port_register($name = 'nmode', $value = $_GET['nmode'], $settings = array('whitelist' => $nmode_whitelist));
        self::port_register($name = 'npipe', $value = $_GET['npipe'], $settings = array('max_length' => 40, 'filter' => NC::FILTER_VAR_NAME));
        self::port_register($name = 'nports', $value = $_GET['nports'], $settings = array('max_length' => 700, 'filter' => NC::FILTER_CUSTOM, 'regex' => '/^[-,a-zA-Z0-9_]+$/'));

        if (self::port_signature($ports = array('nmode', 'nports')))
        {
            // return a JSON array containing validation info
            if($errors = self::port_validate($ports = array('nmode', 'nports')))
            {
                throw new Exception('The values nmode and nports were not valid for this request.');
            }

            $port_names = explode(',', self::port_get('nports'));

            if (!$rows = self::port_validate($port_names))
            {
                return '[]';
            }
            else
            {
                $invalid_values = '[';

                foreach($rows as $name => $row)
                {
                    $invalid_values .= '["'.$name.'","'.str_replace('"', '\"', $row['error_message']).'"]';
                }

                return $invalid_values.']';
            }
        }
        elseif (self::port_signature($ports = array('nmode', 'npipe')))
        {
            if (count(self::port_validate($ports = array('nmode', 'npipe'))))
                throw new Exception('The nmode and npipe ports did not contain valid values.');

            $pipe_name = self::port_get('npipe');
            
            $pipe = self::pipe_get($pipe_name);

            if (!$pipe['settings']['has_output'])
                throw new Exception('The nmode "htmlfrag" cannot be used on a pipe that has "has_output" set to false.');

            $markup_sections = N::markup_get_sections($markup, $type = 'pipe', $name = $pipe_name);
            $rendering_function = $pipe['function'];
            $markup = $rendering_function($markup_sections[1]);
        }
        else
        {
            foreach(self::pipe_get() as $name => $settings)
            {
                // These pipes don't impact output and don't require any pipe comments in the markup of the page (e.g., authorization, etc.)
                if (!$settings['settings']['has_output'])
                {
                    $function = $settings['function'];
                    $function();
                }
                // Standard pipes that impact output and require pipe comments in the markup to show where the results should be placed.
                else
                {
                    $markup_sections = N::markup_get_sections($markup, $type = 'pipe', $name);
                    $rendering_function = $settings['function'];
                    $markup_sections[1] = $rendering_function($markup_sections[1]);
                    $markup = implode('', $markup_sections);
                }
            }
        }

        $markup = self::z_pipe_render_values($markup);

        return $markup;
    }
    public static function z_pipe_render_values($markup)
    {
        return self::row_databind($markup, $row = self::z_values($mode = 'get'), $whitelists = array());
    }
}

echo N::z_pipe_process_registered($markup = ob_get_clean());

?>